﻿using System;
using System.Collections.Generic;
using Bookvar.ObjectModel.Actions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;

namespace Bookvar.UnitTests.ObjectModel.Actions
{
    /// <summary>
    /// Summary description for <see cref="UndoRedoCollectionHelper{T}"/>
    /// </summary>
    [TestClass]
    public class UndoRedoCollectionControllerTests
    {
        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext { get; set; }

        [TestMethod]
        public void CanCreateNew()
        {
            var testController = new UndoRedoCollectionHelper<object>( new List<object>() );
            Assert.IsNotNull( testController );
        }

        [TestMethod]
        [ExpectedException( typeof ( ArgumentNullException ) )]
        public void New_WithNullTargetCollection_Throws()
        {
            new UndoRedoCollectionHelper<object>( null );
        }

        [TestMethod]
        [ExpectedException( typeof ( InvalidOperationException ) )]
        public void Execute_WithNoTarget_Throws()
        {
            var undoRedoHelper = new UndoRedoCollectionHelper<int>();
            undoRedoHelper.Execute();
        }

        [TestMethod]
        public void Execute_WithNoItemsToAddOrRemove_DoesNotThrow()
        {
            var testCollection = new List<int> { 3 };
            var undoRedoHelper = new UndoRedoCollectionHelper<int>( testCollection );
            undoRedoHelper.Execute();

            Assert.IsTrue( testCollection.Count == 1 );
            Assert.AreEqual( 3, testCollection[ 0 ] );
        }

        [TestMethod]
        public void Execute_WithItemsToAdd_AddsTheItemsToTheCollection()
        {
            var testCollection = new List<int>();
            var undoRedoHelper = new UndoRedoCollectionHelper<int>( testCollection );
            undoRedoHelper.ItemsToAdd.Add( 3 );
            undoRedoHelper.ItemsToAdd.Add( 5 );
            undoRedoHelper.Execute();

            Assert.IsTrue( testCollection.Count == 2 );
            CollectionAssert.Contains( testCollection, 3 );
            CollectionAssert.Contains( testCollection, 5 );
        }

        [TestMethod]
        public void Execute_WithItemsToRemove_RemovesTheItemsFromTheCollection()
        {
            var testCollection = new List<int> { 1, 3, 5 };
            var undoRedoHelper = new UndoRedoCollectionHelper<int>( testCollection );
            undoRedoHelper.ItemsToRemove.Add( 3 );
            undoRedoHelper.ItemsToRemove.Add( 5 );
            undoRedoHelper.Execute();

            Assert.IsTrue( testCollection.Count == 1 );
            CollectionAssert.Contains( testCollection, 1 );
        }

        [TestMethod]
        [ExpectedException( typeof ( InvalidOperationException ) )]
        public void Undo_BeforeExecute_Throws()
        {
            var undoRedoHelper = new UndoRedoCollectionHelper<int>( new List<int>() );
            undoRedoHelper.Undo();
        }

        [TestMethod]
        [ExpectedException( typeof ( InvalidOperationException ) )]
        public void Execute_TwiceWithNoUndo_Throws()
        {
            var undoRedoHelper = new UndoRedoCollectionHelper<int>( new List<int>() );
            undoRedoHelper.Execute();
            undoRedoHelper.Execute();
        }

        [TestMethod]
        public void Undo_AfterExecuteWithItemsToAdd_RevertsTheChanges()
        {
            var testCollection = new List<int>();
            var undoRedoHelper = new UndoRedoCollectionHelper<int>( testCollection );
            undoRedoHelper.ItemsToAdd.Add( 3 );
            undoRedoHelper.ItemsToAdd.Add( 5 );
            undoRedoHelper.Execute();
            undoRedoHelper.Undo();

            Assert.IsTrue( testCollection.Count == 0 );
        }

        [TestMethod]
        public void Undo_AfterExecuteWithItemsToRemove_RevertsTheChanges()
        {
            var testCollection = new List<int> { 1, 3, 5 };
            var undoRedoHelper = new UndoRedoCollectionHelper<int>( testCollection );
            undoRedoHelper.ItemsToRemove.Add( 3 );
            undoRedoHelper.ItemsToRemove.Add( 5 );
            undoRedoHelper.Execute();
            undoRedoHelper.Undo();

            Assert.IsTrue( testCollection.Count == 3 );
            CollectionAssert.Contains( testCollection, 1 );
            CollectionAssert.Contains( testCollection, 3 );
            CollectionAssert.Contains( testCollection, 5 );
        }

        [TestMethod]
        //Execute_WithItemsToAddAndToRemove_FirstRemovesTheItemsAndThenAddsTheNewOnes - same test.
        public void Undo_AfterExecuteWithItemsToAddAndToRemove_FirstRevertsTheAddAndThenTheRemove()
        {
            var mocks = new MockRepository();
            var collection = mocks.StrictMock<ICollection<int>>();

            using ( mocks.Ordered() )
            {
                Expect.Call( collection.Contains( 5 ) ).Repeat.Once().Return( true );

                Expect.Call( collection.Remove( 5 ) ).Repeat.Once().Return( true ); // during Execute the item is removed.

                collection.Add( 3 );
                LastCall.Repeat.Once(); // during Execute - the item is added;

                Expect.Call( collection.Remove( 3 ) ).Repeat.Once().Return( true ); // during Undo - add is reverted.
                collection.Add( 5 );
                LastCall.Repeat.Once(); // during Undo - redo is reverted.
            }

            mocks.ReplayAll();

            var undoRedoHelper = new UndoRedoCollectionHelper<int>( collection );
            undoRedoHelper.ItemsToAdd.Add( 3 );
            undoRedoHelper.ItemsToRemove.Add( 5 );
            undoRedoHelper.Execute();
            undoRedoHelper.Undo();

            mocks.VerifyAll();
        }

        [TestMethod]
        public void Execute_WithItemsToRemoveAndSomeAreNotInTheTargetCollection_RemovesOnlyTheItemsFromTheTarget()
        {
            var target = new List<int> { 1, 2 };
            var undoRedoHelper = new UndoRedoCollectionHelper<int>( target );
            undoRedoHelper.ItemsToRemove.Add( 2 );
            undoRedoHelper.ItemsToRemove.Add( 3 ); // not in the target.
            undoRedoHelper.Execute();

            Assert.IsTrue( target.Count == 1 );
            CollectionAssert.Contains( target, 1 );
        }

        [TestMethod]
        public void Undo_WithItemsToRemoveAndSomeAreNotInTheTargetCollection_RevertsOnlyTheRemovedItems()
        {
            var target = new List<int> { 1, 2 };
            var undoRedoHelper = new UndoRedoCollectionHelper<int>( target );
            undoRedoHelper.ItemsToRemove.Add( 2 );
            undoRedoHelper.ItemsToRemove.Add( 3 ); // not in the target.
            undoRedoHelper.Execute();
            undoRedoHelper.Undo();

            Assert.IsTrue( target.Count == 2 );
            CollectionAssert.Contains( target, 1 );
            CollectionAssert.Contains( target, 2 );
        }
    }
}